﻿//-----------------------------------------------------------------------
// <copyright file="TeamFoundationHelper.cs" company="Microsoft Corporation">
//     Copyright (c) Microsoft Corporation 2011. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace Internal.TeamFoundationServer.Submit
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Security.Cryptography;
    using System.Text;
    using System.Windows.Forms;
    using Microsoft.TeamFoundation;
    using Microsoft.TeamFoundation.Build.Client;
    using Microsoft.TeamFoundation.Build.Workflow;
    using Microsoft.TeamFoundation.Build.Workflow.Activities;
    using Microsoft.TeamFoundation.Client;
    using Microsoft.TeamFoundation.VersionControl.Client;
    using Microsoft.TeamFoundation.WorkItemTracking.Client;
    using Properties;

    /// <summary>
    /// TeamFoundationHelper class.
    /// </summary>
    internal class TeamFoundationHelper
    {
        /// <summary>
        /// Prevents a default instance of the <see cref="TeamFoundationHelper"/> class from being created.
        /// </summary>
        private TeamFoundationHelper()
        {
        }

        /// <summary>
        /// Get the list of TFS Collections registered.
        /// </summary>
        /// <returns>Returns a string[] with registered collections.</returns>
        internal static string[] GetRegisteredCollectionNames()
        {
            RegisteredProjectCollection[] projects = RegisteredTfsConnections.GetProjectCollections();

            return projects.Select(project => project.Uri.ToString()).ToArray();
        }

        /// <summary>
        /// Get WorkItem store for a specific collection.
        /// </summary>
        /// <param name="collectionName">Collection name.</param>
        /// <returns>Returns WorkItemStore object.</returns>
        internal static WorkItemStore GetWorkItemStore(string collectionName)
        {
            TfsTeamProjectCollection tfsTeamProjectCollection = GetTfsTeamProjectCollection(collectionName);
            if (tfsTeamProjectCollection != null)
            {
                return tfsTeamProjectCollection.GetService(typeof(WorkItemStore)) as WorkItemStore;
            }

            return null;
        }

        /// <summary>
        /// Get Workspace Names.
        /// </summary>
        /// <param name="collectionName">Collection name.</param>
        /// <returns>Returns the workspace list names.</returns>
        internal static string[] GetWorkspaceNames(string collectionName)
        {
            var workspaceList = new List<string>();

            string authorizedUser;
            Workspace[] workspaces = GetTfsWorkspaces(collectionName, null, out authorizedUser);
            if (workspaces != null)
            {
                workspaceList.AddRange(workspaces.Select(workspace => workspace.Name));
            }

            return workspaceList.ToArray();
        }

        /// <summary>
        /// Get Workspace.
        /// </summary>
        /// <param name="collectionName">Collection name.</param>
        /// <param name="workspaceName">Workspace name.</param>
        /// <returns>Returns the workspace object.</returns>
        internal static Workspace GetWorkspace(string collectionName, string workspaceName)
        {
            string authorizedUser;
            Workspace[] workspaces = GetTfsWorkspaces(collectionName, workspaceName, out authorizedUser);
            if (workspaces != null && workspaces.Length > 0)
            {
                return workspaces[0];
            }

            return null;
        }

        /// <summary>
        /// Get Shelve sets.
        /// </summary>
        /// <param name="collectionName">Collection name.</param>
        /// <param name="workspaceName">Workspace name.</param>
        /// <returns>Returns the shelve set list.</returns>
        internal static List<Shelveset> GetShelvesets(string collectionName, string workspaceName)
        {
            var shelvesetList = new List<Shelveset>();

            string authorizedUser;
            Workspace[] workspaces = GetTfsWorkspaces(collectionName, workspaceName, out authorizedUser);
            if (workspaces != null && workspaces.Length > 0)
            {
                PendingSet[] pendingSets = workspaces[0].QueryShelvedChanges(null, authorizedUser, null);
                if (pendingSets != null)
                {
                    shelvesetList.AddRange(
                        pendingSets.Select(pendingSet => workspaces[0].VersionControlServer.QueryShelvesets(pendingSet.Name, authorizedUser))
                        .Where(shelvesets => shelvesets != null)
                        .SelectMany(shelvesets => shelvesets)
                        .OrderByDescending(shelvesets => shelvesets.CreationDate));
                }
            }

            return shelvesetList;
        }

        /// <summary>
        /// Get Shelve sets.
        /// </summary>
        /// <param name="collectionName">Collection name.</param>
        /// <param name="owner">Owner name.</param>
        /// <returns>Returns the shelve set list.</returns>
        internal static List<Shelveset> GetShelvesetsByOwner(string collectionName, string owner)
        {
            return GetShelvesetsByOwner(collectionName, owner, null);
        }

        /// <summary>
        /// Get Shelve sets.
        /// </summary>
        /// <param name="collectionName">Collection name.</param>
        /// <param name="owner">Owner name.</param>
        /// <param name="shelveSetName">ShelveSet name.</param>
        /// <returns>Returns the shelve set list.</returns>
        internal static List<Shelveset> GetShelvesetsByOwner(string collectionName, string owner, string shelveSetName)
        {
            var shelvesetList = new List<Shelveset>();

            var sourceControl = GetTfsSourceControl(collectionName);
            if (sourceControl != null)
            {
                try
                {
                    shelvesetList.AddRange(sourceControl.QueryShelvesets(shelveSetName, owner));
                }
                catch (AuthorizationException err)
                {
                    MessageBox.Show(
                        err.Message,
                        Resources.ErrorString,
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Error,
                        MessageBoxDefaultButton.Button1,
                        MessageBoxOptions.RightAlign & MessageBoxOptions.RtlReading);

                    return null;
                }
                catch (IdentityNotFoundException err)
                {
                    MessageBox.Show(
                        err.Message,
                        Resources.ErrorString,
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Error,
                        MessageBoxDefaultButton.Button1,
                        MessageBoxOptions.RightAlign & MessageBoxOptions.RtlReading);

                    return null;
                }
            }

            return shelvesetList;
        }

        /// <summary>
        /// Delete Shelve set.
        /// </summary>
        /// <param name="collectionName">Collection name.</param>
        /// <param name="workspaceName">Workspace name.</param>
        /// <param name="shelveSetName">ShelveSet name.</param>
        internal static void DeleteShelvesets(string collectionName, string workspaceName, string shelveSetName)
        {
            string authorizedUser;
            var workspaces = GetTfsWorkspaces(collectionName, workspaceName, out authorizedUser);
            if (workspaces != null && workspaces.Length > 0)
            {
                try
                {
                    workspaces[0].VersionControlServer.DeleteShelveset(shelveSetName, authorizedUser);
                }
                catch (ShelvesetNotFoundException)
                {
                    // Ignore - There is nothing we can do here.
                }
                catch (InvalidOperationException)
                {
                    // Ignore - There is nothing we can do here.
                }
            }
        }

        /// <summary>
        /// Get Pending changes.
        /// </summary>
        /// <param name="collectionName">Collection name.</param>
        /// <param name="workspaceName">Workspace name.</param>
        /// <returns>Returns the pending changes items.</returns>
        internal static PendingChange[] GetPendingChanges(string collectionName, string workspaceName)
        {
            string authorizedUser;
            Workspace[] workspaces = GetTfsWorkspaces(collectionName, workspaceName, out authorizedUser);
            if (workspaces != null && workspaces.Length > 0)
            {
                return workspaces[0].GetPendingChanges();
            }

            return null;
        }

        /// <summary>
        /// Get Pending changes based on a specific shelve set name.
        /// </summary>
        /// <param name="collectionName">Collection name.</param>
        /// <param name="workspaceName">Workspace name.</param>
        /// <param name="shelvesetName">Shelve set name.</param>
        /// <returns>Returns the pending changes items.</returns>
        internal static PendingChange[] GetPendingChangesByShelveSetName(string collectionName, string workspaceName, string shelvesetName)
        {
            string authorizedUser;
            Workspace[] workspaces = GetTfsWorkspaces(collectionName, workspaceName, out authorizedUser);
            if (workspaces != null && workspaces.Length > 0)
            {
                PendingSet[] pendingSets = workspaces[0].QueryShelvedChanges(shelvesetName, authorizedUser, null);
                if (pendingSets != null && pendingSets.Length > 0)
                {
                    return pendingSets[0].PendingChanges;
                }
            }

            return null;
        }

        /// <summary>
        /// Get Pending changes based on a specific shelve set object.
        /// </summary>
        /// <param name="shelveSet">ShelveSet object.</param>
        /// <returns>Returns the pending changes items.</returns>
        internal static PendingChange[] GetPendingChangesByShelveSet(Shelveset shelveSet)
        {
            var pendingSet = shelveSet.VersionControlServer.QueryShelvedChanges(shelveSet);
            return pendingSet[0].PendingChanges;
        }

        /// <summary>
        /// Verify if Item has any conflict.
        /// </summary>
        /// <param name="collectionName">Collection name.</param>
        /// <param name="workspaceName">Workspace name.</param>
        /// <param name="itemChange">Item change object..</param>
        /// <returns>String with conflict description if any.</returns>
        internal static string GetItemConflictStatus(string collectionName, string workspaceName, PendingChange itemChange)
        {
            string authorizedUser;
            Workspace[] workspaces = GetTfsWorkspaces(collectionName, workspaceName, out authorizedUser);
            if (workspaces != null && workspaceName.Length > 0)
            {
                var checkedWorkItems = new WorkItemCheckinInfo[0];
                CheckinEvaluationResult checkinEvaluationResult = workspaces[0].EvaluateCheckin2(
                    CheckinEvaluationOptions.Conflicts,
                    null,
                    new[] { itemChange },
                    null,
                    null,
                    checkedWorkItems);

                if (checkinEvaluationResult.Conflicts.Length > 0)
                {
                    var conflict = new StringBuilder();
                    foreach (var checkinConflict in checkinEvaluationResult.Conflicts)
                    {
                        conflict.Append(checkinConflict.Message + " ");
                    }

                    return conflict.ToString();
                }
            }

            return null;
        }

        /// <summary>
        /// Evaluate check-in.
        /// </summary>
        /// <param name="collectionName">Collection name.</param>
        /// <param name="workspaceName">Workspace name.</param>
        /// <param name="shelvesetName">Shelve set name.</param>
        /// <returns>String with conflict description if any.</returns>
        internal static string EvaluateCheckin(string collectionName, string workspaceName, string shelvesetName)
        {
            string authorizedUser;
            Workspace[] workspaces = GetTfsWorkspaces(collectionName, workspaceName, out authorizedUser);
            if (workspaces != null && workspaces.Length > 0)
            {
                PendingSet[] pendingSets = workspaces[0].QueryShelvedChanges(shelvesetName, authorizedUser, null);
                if (pendingSets != null && pendingSets.Length > 0)
                {
                    PendingChange[] pendingChanges = pendingSets[0].PendingChanges;
 
                    var checkedWorkItems = new WorkItemCheckinInfo[0];
                    CheckinEvaluationResult checkinEvaluationResult = workspaces[0].EvaluateCheckin2(
                        CheckinEvaluationOptions.All,
                        pendingChanges,
                        pendingChanges,
                        null,
                        null,
                        checkedWorkItems);

                    if (checkinEvaluationResult.PolicyFailures.Length > 0)
                    {
                        var failure = new StringBuilder();
                        foreach (PolicyFailure policyFailure in checkinEvaluationResult.PolicyFailures)
                        {
                            var policyBase = policyFailure.Policy as PolicyBase;
                            if (policyBase != null)
                            {
                                failure.Append(policyBase.Type + " - " + policyBase.TypeDescription + Environment.NewLine);
                            }
                        }

                        return failure.ToString();
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Get TeamProject name By LocalItem.
        /// </summary>
        /// <param name="collectionName">Collection name.</param>
        /// <param name="workspaceName">Workspace name.</param>
        /// <param name="localOrServerItem">Local or Server item.</param>
        /// <returns>Returns team project name.</returns>
        internal static string GetTeamProjectNameByLocalItem(string collectionName, string workspaceName, string localOrServerItem)
        {
            string authorizedUser;
            var workspaces = GetTfsWorkspaces(collectionName, workspaceName, out authorizedUser);
            if (workspaces != null && workspaces.Length > 0)
            {
                TeamProject teamProject = null;
                try
                {
                    try
                    {
                        teamProject = workspaces[0].GetTeamProjectForLocalPath(localOrServerItem);
                    }
                    catch (InvalidPathException)
                    {
                        teamProject = workspaces[0].VersionControlServer.GetTeamProjectForServerPath(localOrServerItem);
                    }
                    catch (ItemNotMappedException)
                    {
                        teamProject = workspaces[0].VersionControlServer.GetTeamProjectForServerPath(localOrServerItem);
                    }
                }
                catch (VersionControlException err)
                {
                    MessageBox.Show(
                        err.Message,
                        Resources.ErrorString,
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Error,
                        MessageBoxDefaultButton.Button1,
                        MessageBoxOptions.RightAlign & MessageBoxOptions.RtlReading);
                }

                if (teamProject != null)
                {
                    return teamProject.Name;
                }
            }

            return null;
        }

        /// <summary>
        /// Submit Gated Check-in.
        /// </summary>
        /// <param name="tfsTeamProjectCollection">TeamProjectCollection object.</param>
        /// <param name="bldDefinitionUri">Build definition.</param>
        /// <param name="shelvesetName">Shelve set Name.</param>
        /// <param name="dropLocation">Drop Location folder.</param>
        /// <returns>Returns QueuedBuild object.</returns>
        internal static IQueuedBuild SubmitGatedCheckin(TfsTeamProjectCollection tfsTeamProjectCollection, Uri bldDefinitionUri, string shelvesetName, string dropLocation = null)
        {
            var buildServer = tfsTeamProjectCollection.GetService<IBuildServer>();
            var buildDefinition = buildServer.GetBuildDefinition(bldDefinitionUri, QueryOptions.Definitions);

            return ValidateChanges(buildServer, buildDefinition, shelvesetName, true /* build in parallel */, false /* incremental build */, dropLocation, true /* check-in if it passes */);
        }

        /// <summary>
        /// Validate Changes.
        /// </summary>
        /// <param name="buildServer">BuildServer object.</param>
        /// <param name="buildDefinition">Build definition.</param>
        /// <param name="shelvesetName">Shelve set Name.</param>
        /// <param name="buildInParallel">Build In Parallel.</param>
        /// <param name="incrementalBuild">Incremental Build.</param>
        /// <param name="dropLocation">Drop Location folder.</param>
        /// <param name="checkinIfPasses">True if you would like to check-in the changes if validation passes.</param>
        /// <returns>Returns QueuedBuild object.</returns>
        internal static IQueuedBuild ValidateChanges(
            IBuildServer buildServer, 
            IBuildDefinition buildDefinition, 
            string shelvesetName, 
            bool buildInParallel, 
            bool incrementalBuild, 
            string dropLocation = null, 
            bool checkinIfPasses = false)
        {
            try
            {
                if (buildDefinition != null)
                {
                    var processParameters = WorkflowHelpers.DeserializeProcessParameters(buildDefinition.ProcessParameters);
                    if (!processParameters.ContainsKey("MSBuildArguments"))
                    {
                        processParameters.Add(new KeyValuePair<string, object>("MSBuildArguments", string.Empty));
                    }

                    if (!processParameters.ContainsKey("CleanWorkspace"))
                    {
                        processParameters.Add(new KeyValuePair<string, object>("CleanWorkspace", new CleanWorkspaceOption()));
                    }

                    if (buildInParallel)
                    {
                        processParameters["MSBuildArguments"] += " /m /p:BuildInParallel=true ";
                    }

                    if (incrementalBuild)
                    {
                        processParameters["CleanWorkspace"] = CleanWorkspaceOption.None;
                    }

                    var buildRequest = buildDefinition.CreateBuildRequest();
                    buildRequest.ProcessParameters = WorkflowHelpers.SerializeProcessParameters(processParameters);
                    buildRequest.ShelvesetName = shelvesetName;
                    buildRequest.Reason = checkinIfPasses ? BuildReason.CheckInShelveset : BuildReason.ValidateShelveset;
                    buildRequest.RequestedFor = string.Format(CultureInfo.InvariantCulture, @"{0}\{1}", Environment.UserDomainName, Environment.UserName);
                    if (!string.IsNullOrEmpty(dropLocation))
                    {
                        buildRequest.DropLocation = dropLocation;
                    }

                    return buildServer.QueueBuild(buildRequest);
                }
            }
            catch (BuildServerException err)
            {
                MessageBox.Show(
                    err.Message,
                    Resources.ErrorString,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error,
                    MessageBoxDefaultButton.Button1,
                    MessageBoxOptions.RightAlign & MessageBoxOptions.RtlReading);
            }

            return null;
        }

        /// <summary>
        /// Get ChangeSet URL.
        /// </summary>
        /// <param name="tfsTeamProjectCollection">TeamProjectCollection object.</param>
        /// <param name="changesetId">Change set Id.</param>
        /// <returns>Return the URL that points to the change set.</returns>
        internal static Uri GetChangeSetUrl(TfsTeamProjectCollection tfsTeamProjectCollection, int changesetId)
        {
            var tswaClientHyperlinkService = tfsTeamProjectCollection.GetService<TswaClientHyperlinkService>();
            if (tswaClientHyperlinkService != null)
            {
                return tswaClientHyperlinkService.GetChangesetDetailsUrl(changesetId);
            }

            return null;
        }

        /// <summary>
        /// Get Team Project Collection.
        /// </summary>
        /// <param name="collectionName">Collection name.</param>
        /// <returns>Returns Team Project Collection object.</returns>
        internal static TfsTeamProjectCollection GetTfsTeamProjectCollection(string collectionName)
        {
            try
            {
                var tfsTeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(collectionName));
                tfsTeamProjectCollection.EnsureAuthenticated();

                return tfsTeamProjectCollection;
            }
            catch (TeamFoundationServerUnauthorizedException err)
            {
                MessageBox.Show(
                    err.ToString(),
                    Resources.ErrorString,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error,
                    MessageBoxDefaultButton.Button1,
                    MessageBoxOptions.RightAlign & MessageBoxOptions.RtlReading);
            }
            catch (TeamFoundationServiceUnavailableException err)
            {
                MessageBox.Show(
                    err.ToString(),
                    Resources.ErrorString,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error,
                    MessageBoxDefaultButton.Button1,
                    MessageBoxOptions.RightAlign & MessageBoxOptions.RtlReading);
            }

            return null;
        }

        /// <summary>
        /// The file has changes.
        /// </summary>
        /// <param name="pendingChange">The pending change.</param>
        /// <returns>The <see cref="bool"/>.</returns>
        internal static bool FileHasChanges(PendingChange pendingChange)
        {
            if (pendingChange.IsAdd || pendingChange.IsDelete || pendingChange.IsRename || pendingChange.IsRollback)
            {
                return true;
            }

            var fileName = string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", Path.GetTempPath(), Guid.NewGuid(), ".tmp");
            try
            {
                pendingChange.VersionControlServer.DownloadFile(pendingChange.ServerItem, fileName);
            }
            catch (VersionControlException)
            {
                return true;
            }

            if (!File.Exists(fileName))
            {
                return true;
            }

            string hash1;
            var fileBytes = File.ReadAllBytes(fileName);
            using (var cryptoProvider = new SHA1CryptoServiceProvider())
            {
                hash1 = BitConverter.ToString(cryptoProvider.ComputeHash(fileBytes));
            }

            File.Delete(fileName);

            if (!File.Exists(pendingChange.LocalItem))
            {
                return true;
            }

            fileBytes = File.ReadAllBytes(pendingChange.LocalItem);
            string hash2;
            using (var cryptoProvider = new SHA1CryptoServiceProvider())
            {
                hash2 = BitConverter.ToString(cryptoProvider.ComputeHash(fileBytes));
            }

            if (hash1 == hash2)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Get Workspaces object.
        /// </summary>
        /// <param name="collectionName">Collection name.</param>
        /// <param name="workspaceName">Workspace name.</param>
        /// <param name="authorizedUser">Out parameter, returns Source control authorized user.</param>
        /// <returns>Array with all workspaces.</returns>
        private static Workspace[] GetTfsWorkspaces(string collectionName, string workspaceName, out string authorizedUser)
        {
            authorizedUser = null;

            try
            {
                var sourceControl = GetTfsSourceControl(collectionName);
                if (sourceControl != null)
                {
                    authorizedUser = sourceControl.AuthorizedUser;
                    return sourceControl.QueryWorkspaces(workspaceName, sourceControl.AuthorizedUser, System.Net.Dns.GetHostName(), WorkspacePermissions.CheckIn);
                }
            }
            catch (TeamFoundationServiceUnavailableException err)
            {
                MessageBox.Show(
                    err.ToString(),
                    Resources.ErrorString,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error,
                    MessageBoxDefaultButton.Button1,
                    MessageBoxOptions.RightAlign & MessageBoxOptions.RtlReading);
            }

            return null;
        }

        /// <summary>
        /// Get TFS Source Control.
        /// </summary>
        /// <param name="collectionName">Collection name.</param>
        /// <returns>Source control object.</returns>
        private static VersionControlServer GetTfsSourceControl(string collectionName)
        {
            TfsTeamProjectCollection tfsTeamProjectCollection = GetTfsTeamProjectCollection(collectionName);
            if (tfsTeamProjectCollection != null)
            {
                return tfsTeamProjectCollection.GetService<VersionControlServer>();
            }

            return null;
        }
    }
}
